package com.bitbakery.plugin.lisp.lang.formatting;

import com.bitbakery.plugin.lisp.lang.psi.CLFile;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

public class LispBlock implements Block {
    final private ASTNode myNode;
    final private Alignment myAlignment;
    final private Indent myIndent;
    final private CodeStyleSettings mySettings;

    private Wrap myWrap;
    private List<Block> mySubBlocks = null;

    public LispBlock(@NotNull final ASTNode node,
                     @Nullable final Alignment alignment,
                     @NotNull final Indent indent,
                     @Nullable final Wrap wrap,
                     final CodeStyleSettings settings) {
        myNode = node;
        myAlignment = alignment;
        myIndent = indent;
        myWrap = wrap;
        mySettings = settings;
    }


    @NotNull
    public ASTNode getNode() {
        return myNode;
    }

    @NotNull
    public CodeStyleSettings getSettings() {
        return mySettings;
    }


    @NotNull
    public TextRange getTextRange() {
        return myNode.getTextRange();
    }

    @NotNull
    public List<Block> getSubBlocks() {
        if (mySubBlocks == null) {
            mySubBlocks = generateSubBlocks();
        }
        return mySubBlocks;
    }

    @Nullable
    public Wrap getWrap() {
        return myWrap;
    }


    @Nullable
    public Indent getIndent() {
        return myIndent;
    }


    @Nullable
    public Alignment getAlignment() {
        return myAlignment;
    }

    @Nullable
    public Spacing getSpacing(@NotNull final Block child1, @NotNull final Block child2) {
        return LispSpacingProcessor.getSpacing(
                this, ((LispBlock) child1).getNode(), ((LispBlock) child2).getNode(), mySettings);
    }


    @NotNull
    public ChildAttributes getChildAttributes(final int newChildIndex) {
        final PsiElement psiParent = getNode().getPsi();

        if (psiParent instanceof CLFile) {
            return new ChildAttributes(Indent.getNoneIndent(), null);
        }

        return new ChildAttributes(Indent.getNoneIndent(), null);
    }

    public boolean isIncomplete() {
        return isIncomplete(myNode);
    }


    public boolean isLeaf() {
        return isLeaf(myNode);
    }

    private boolean isLeaf(@NotNull final ASTNode node) {
        return node.getFirstChildNode() == null;
    }

    private List<Block> generateSubBlocks() {
        final ArrayList<Block> subBlocks = new ArrayList<Block>();

        // Get all the children with null filter
        ASTNode children[] = myNode.getChildren(null);
        int childNumber = children.length;
        if (childNumber == 0) {
            return subBlocks;
        }

        ASTNode prevChildNode = null;
        for (int i = 0; i < childNumber; i++) {
            final ASTNode childNode = children[i];
            if (canBeCorrectBlock(childNode)) {
                final Wrap wrap = LispWrapProcessor.getChildWrap(this, childNode, childNumber, i);
                final Indent indent = LispIndentProcessor.getChildIndent(this, prevChildNode, childNode);
                subBlocks.add(new LispBlock(childNode, null, indent, wrap, mySettings));
                prevChildNode = childNode;
            }
        }
        return subBlocks;
    }


    /**
     * @param node Tree node
     * @return true if node is incomplete
     */
    private static boolean isIncomplete(@NotNull final ASTNode node) {
        ASTNode lastChild = node.getLastChildNode();
        while (lastChild != null &&
                (lastChild.getPsi() instanceof PsiWhiteSpace || lastChild.getPsi() instanceof PsiComment)) {
            lastChild = lastChild.getTreePrev();
        }
        if (lastChild == null) {
            return false;
        }

        if (lastChild.getPsi() instanceof PsiErrorElement) {
            return true;
        }
        return isIncomplete(lastChild);
    }

    /**
     * @param node Tree node
     * @return true, if the current node can be block node, else otherwise
     */
    private static boolean canBeCorrectBlock(@NotNull final ASTNode node) {
        return (node.getText().trim().length() > 0);
    }

    public String toString() {
        return "Block at: " + myNode.toString();
    }
}
